<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="../css/main.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/highlight.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/print.css" media="print" rel="stylesheet" type="text/css" />
<title>第17章 - symfonyを拡張する</title>
</head>

<body>
<div class="navigation">

<table width="100%">
<tr>
<td width="40%" align="left"><a href="16-Application-Management-Tools.html">前の章</a></td>
<td width="20%" align="center"><a href="index.html">ホーム</a></td>
<td width="40%" align="right"><a href="18-Performance.html">次の章</a></td>
</tr>
</table>
<hr/>
</div>

<div>
<a name="chapter.17.extending.symfony" id="chapter.17.extending.symfony"></a><h1>第17章 - symfonyを拡張する</h1>

<p>結局のところ、symfonyのふるまいを変える必要があります。特定のクラスのふるまいを修正するもしくは独自のカスタム機能を追加する必要があるにせよ、変更作業は必然です。フレームワークが予想できない固有の要望を顧客が持つからです。実際のところ、この状況はとてもありふれているのでsymfnyは単純なクラスの継承を越えて、実行時に既存のクラスを拡張する方法を提供します。ファクトリ(factory)の設定を利用することで、symfonyのコアクラスを独自のものに置き換えできます。いったん拡張機能(エクステンション)を作れば、それをプラグインとして簡単にパッケージにできるので、別のアプリケーションもしくは別のsymfonyのユーザーが再利用できます。</p>

<div class="toc">
<dl>
<dt><a href="#events">17.1. イベント</a></dt>
<dd><dl>
<dt><a href="#understanding.events">17.1.1. イベントを理解する</a></dt>
<dt><a href="#notifying.an.event">17.1.2. イベントを通知する</a></dt>
<dt><a href="#notifying.an.event.until.a.listener.handles.it">17.1.3. リスナーがイベントを扱うまでイベントを通知する</a></dt>
<dt><a href="#changing.the.return.value.of.a.method">17.1.4. メソッドの戻り値を変更する</a></dt>
<dt><a href="#builtin.events">17.1.5. 組み込みのイベント</a></dt>
<dt><a href="#where.to.register.listeners">17.1.6. リスナーを登録する場所は？</a></dt>
</dl></dd>
<dt><a href="#factories">17.2. ファクトリ</a></dt>
<dt><a href="#integrating.with.other.frameworks.components">17.3. ほかのフレームワークのコンポーネントと統合する</a></dt>
<dt><a href="#plugins">17.4. プラグイン</a></dt>
<dd><dl>
<dt><a href="#finding.symfony.plugins">17.4.1. symfonyのプラグインを見つける</a></dt>
<dt><a href="#installing.a.plugin">17.4.2. プラグインをインストールする</a></dt>
<dt><a href="#pear.plugins">17.4.3. PEARプラグイン</a></dt>
<dd><dl>
<dt><a href="#archive.plugins">17.4.3.1. アーカイブのプラグイン</a></dt>
<dt><a href="#installing.plugins.from.a.version.control.repository">17.4.3.2. バージョン管理システムのリポジトリからプラグインをインストールする</a></dt>
<dt><a href="#activating.a.plugin.module">17.4.3.3. プラグインモジュールを有効にする</a></dt>
<dt><a href="#listing.the.installed.plugins">17.4.3.4. インストールしたプラグインの一覧を表示する</a></dt>
<dt><a href="#upgrading.and.uninstalling.plugins">17.4.3.5. プラグインのアップグレードとアンインストール</a></dt>
</dl></dd>
<dt><a href="#anatomy.of.a.plugin">17.4.4. プラグインの分析</a></dt>
<dd><dl>
<dt><a href="#plugin.file.structure">17.4.4.1. プラグインのファイル構造</a></dt>
<dt><a href="#plugin.abilities">17.4.4.2. プラグインの機能</a></dt>
<dt><a href="#manual.plugin.setup">17.4.4.3. 手動によるプラグインのセットアップ</a></dt>
<dt><a href="#customizing.a.plugin.for.an.application">17.4.4.4. アプリケーションのためにプラグインをカスタマイズする</a></dt>
</dl></dd>
<dt><a href="#how.to.write.a.plugin">17.4.5. プラグインの書き方</a></dt>
<dd><dl>
<dt><a href="#file.organization">17.4.5.1. ファイルのコンフィギュレーション</a></dt>
<dt><a href="#creating.the.package.xml.file">17.4.5.2. package.xmlファイルを作る</a></dt>
<dt><a href="#contents">17.4.5.3. 内容</a></dt>
<dt><a href="#plugin.dependencies">17.4.5.4. プラグインの依存関係</a></dt>
<dt><a href="#building.the.plugin">17.4.5.5. プラグインをビルドする</a></dt>
<dt><a href="#hosting.your.plugin.in.the.symfony.project.website">17.4.5.6. 公式サイトでプラグインを配布する</a></dt>
<dt><a href="#naming.conventions">17.4.5.7. 命名規約</a></dt>
</dl></dd></dl></dd>
<dt><a href="#summary">17.5. まとめ</a></dt>
</dl>
</div>
<a name="events" id="events"></a><h2>イベント</h2>

<p>PHPの現在の制限のなかで、もっとも悩ましいことは複数のクラスを継承できるクラスを持ていないことです。別の制限は既存のクラスに新しいクラスを追加できないこともしくは既存のクラスをオーバーライドできないことです。これらの2つの制限を緩和してsymfonyフレームワークを本当に拡張できるものにするために、symfonyは<em>イベントシステム</em>を導入します。この機能はCocoaフレームワークのイベントマネージャにインスパイアされObserverデザインパターンに基づいています(<a href="http://en.wikipedia.org/wiki/Observer_pattern">http://en.wikipedia.org/wiki/Observer_pattern</a>)。</p>

<a name="understanding.events" id="understanding.events"></a><h3>イベントを理解する</h3>

<p>symfonyのクラスのなかにはさまざまな瞬間に"イベントを通知する"ものがあります。たとえば、ユーザーがcultureを変更するとき、ユーザーオブジェクトは<code>change_culture</code>イベントを通知します。このイベントはプロジェクト空間のなかで大声でつぎの内容を叫ぶことに似ています: "それをやっています。あなたがお望みのことは何でもしてください。"</p>

<p>イベントが起きるときに何か特別なことを実行することに決めることができます。たとえば、ユーザーが望むcultureを記録するために、<code>change_culture</code>イベントが通知されるたびにユーザーのcultureをデータベースのテーブルに保存できます。これを行うには、<em>イベントリスナーを登録する必要があります</em>。イベントリスナー(event listener)はイベントが起きるときに呼び出される関数を宣言することを伝える複雑なステートメントです。リスト17-1はリスナーを<code>change_culture</code>イベントに登録する方法を示してます。</p>

<p>リスト17-1 - イベントリスナーを登録する</p>

<pre class="php"><span class="re0">$dispatcher</span><span class="sy0">-&gt;</span><span class="me1">connect</span><span class="br0">&#40;</span><span class="st_h">'user.change_culture'</span><span class="sy0">,</span> <span class="st_h">'changeUserCulture'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="kw2">function</span> changeUserCulture<span class="br0">&#40;</span>sfEvent <span class="re0">$event</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="re0">$user</span> <span class="sy0">=</span> <span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">getSubject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="re0">$culture</span> <span class="sy0">=</span> <span class="re0">$event</span><span class="br0">&#91;</span><span class="st_h">'culture'</span><span class="br0">&#93;</span><span class="sy0">;</span>
&nbsp;
  <span class="co1">// ユーザーのcultureで何かを行う</span>
<span class="br0">&#125;</span></pre>

<p>すべてのイベントとリスナーの登録は<em>Event Dispatcher</em>と呼ばれる特別なオブジェクトによって管理されます。このオブジェクトはSingletonの<code>sfContext</code>を利用することでsymfonyのどこからでもアクセス可能で、たいていのsymfonyのオブジェクトはそれにアクセスできる<code>getEventDispatcher()</code>メソッドを提供します。ディスパッチャの<code>connect()</code>メソッドを利用することで、イベントが起きるときに呼び出されるPHPのcallable(クラスのメソッドもしくは関数)を登録できます。<code>connect()</code>の最初の引数はイベントの識別子で、名前空間と名前で構成される文字列です。2番目の引数はPHPのcallableです。</p>

<p>いったん関数がEvent Dispatcherに登録されると、イベントが停止されるまで待機します。Event Dispatcherはすべてのイベントリスナーの記録を行い、イベントが通知されるときにどれが呼び出されるのかを知っています。これらのメソッドもしくは関数を呼び出すとき、ディスパッチャはこれらに<code>sfEvent</code>オブジェクトをパラメーターとして渡します。</p>

<p>イベントオブジェクトは通知されたイベントに関する情報を保存します。<code>getSubject()</code>メソッドのおかげでイベント通知機能(notifier)を読みとることが可能で、イベントパラメーターはイベントオブジェクトを配列として利用することでアクセスできます(たとえば、<code>user.change_culture</code>を通知するとき<code>sfUser</code>によって渡される<code>culture</code>パラメーターを読みとるために<code>$event['culture']</code>を利用できます)。</p>

<p>まとめると、イベントシステムによって、継承を利用せずに、既存のクラスに機能を追加するもしくは実行時にメソッドを修正できるようになります。</p>

<blockquote class="note"><p>: バージョン1.0において、symfonyはよく似ているが異なる構文を持つシステムを利用していました。Event Dispatcherのメソッドを呼び出す変わりに、イベントを登録して通知する<code>sfMixer</code>クラスのstaticなメソッドの呼び出しを見ることがあります。<code>sfMixer</code>の呼び出しは非推奨ですが、symfony 1.1でもまだ動作します。</p>
</blockquote>

<a name="notifying.an.event" id="notifying.an.event"></a><h3>イベントを通知する</h3>

<p>symfonyのクラスがイベントを通知するように、独自のクラスは実行時の拡張性を提供し特定の状況時にイベントを通知できます。 たとえば、アプリケーションがいくつかのサードパーティのWebサービスをリクエストしこれらのリクエストのRESTロジックをラップするために<code>sfRestRequest</code>クラスを書いた場合を考えてみましょう。このクラスが新たにリクエストするたびにイベントを通知することはよいアイデアといえます。これによって将来のロギング機能もしくはキャッシュ機能の追加が簡単になります。リスト17-2はイベントを通知するために既存の<code>fetch()</code>メソッドに追加するコードを示してます。</p>

<p>リスト17-2 - イベントを通知する</p>

<pre class="php"><span class="kw2">class</span> sfRestRequest
<span class="br0">&#123;</span>
  protected <span class="re0">$dispatcher</span> <span class="sy0">=</span> <span class="kw4">null</span><span class="sy0">;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __construct<span class="br0">&#40;</span>sfEventDispatcher <span class="re0">$dispatcher</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span> <span class="sy0">=</span> <span class="re0">$dispatcher</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co4">/**
   * 外部のWebサービスにクエリを行う
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> fetch<span class="br0">&#40;</span><span class="re0">$uri</span><span class="sy0">,</span> <span class="re0">$parameters</span> <span class="sy0">=</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// 取得プロセスの開始を通知する</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">notify</span><span class="br0">&#40;</span><span class="kw2">new</span> sfEvent<span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">,</span> <span class="st_h">'rest_request.fetch_prepare'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span>
      <span class="st_h">'uri'</span>        <span class="sy0">=&gt;</span> <span class="re0">$uri</span><span class="sy0">,</span>
      <span class="st_h">'parameters'</span> <span class="sy0">=&gt;</span> <span class="re0">$parameters</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="co1">// リクエストを行い結果を変数$resultに保存する</span>
    <span class="co1">// ...</span>
&nbsp;
    <span class="co1">// 取得プロセスの終了を通知する</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">notify</span><span class="br0">&#40;</span><span class="kw2">new</span> sfEvent<span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">,</span> <span class="st_h">'rest_request.fetch_success'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span>
      <span class="st_h">'uri'</span>        <span class="sy0">=&gt;</span> <span class="re0">$uri</span><span class="sy0">,</span>
      <span class="st_h">'parameters'</span> <span class="sy0">=&gt;</span> <span class="re0">$parameters</span><span class="sy0">,</span>
      <span class="st_h">'result'</span>     <span class="sy0">=&gt;</span> <span class="re0">$result</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="kw1">return</span> <span class="re0">$result</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p>Event Dispatcherの<code>notify()</code>メソッドは<code>sfEvent</code>オブジェクトをパラメーターとして必要とします; これはイベントリスナーに渡されるオブジェクトそのものです。このオブジェクトはつねに参照を通知機能とイベント識別子に運びます(これがイベントのインスタンスが<code>this</code>で初期化される理由)。オプションとして、これはパラメーターの連想配列を受けとります。これはリスナーに通知機能のロジックと情報をやりとりする方法を提供します。</p>

<blockquote class="tip"><p>
  イベントを通知するクラスだけがイベントシステムの方法によって拡張できます。将来クラスを拡張する必要があるかわからない場合、重要なメソッドに通知機能を追加することはつねによい考えです。</p>
</blockquote>

<a name="notifying.an.event.until.a.listener.handles.it" id="notifying.an.event.until.a.listener.handles.it"></a><h3>リスナーがイベントを扱うまでイベントを通知する</h3>

<p><code>notify()</code>メソッドを利用することで、通知されたイベントに登録されたすべてのリスナーが実行されることを確認できます。しかしいくつかの場合において、リスナーがイベントを停止させて通知されないようにさせることが必要です。この場合、<code>notify()</code>メソッドの代わりに<code>notifyUntil()</code>メソッドを使うべきです。ディスパッチャは特定のリスナーが<code>true</code>を返すまですべてのリスナーを実行します。言い換えると、<code>notifyUntil()</code>メソッドはプロジェクトの空間で"やってます。誰かが気づいたら、誰かに伝えることを止めます。"ということを伝えるようなものです。リスト17-3はメソッドを追加するために<code>__call()</code>マジックメソッドを組み合わせるテクニックの使いかたを示しています。</p>

<p>リスト17-3 - リスナーがtrueを返すまでイベントを通知する</p>

<pre class="php"><span class="kw2">class</span> sfRestRequest
<span class="br0">&#123;</span>
  <span class="co1">// ...</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __call<span class="br0">&#40;</span><span class="re0">$method</span><span class="sy0">,</span> <span class="re0">$arguments</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$event</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">notifyUntil</span><span class="br0">&#40;</span><span class="kw2">new</span> sfEvent<span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">,</span> <span class="st_h">'rest_request.method_not_found'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span>
      <span class="st_h">'method'</span>    <span class="sy0">=&gt;</span> <span class="re0">$method</span><span class="sy0">,</span> 
      <span class="st_h">'arguments'</span> <span class="sy0">=&gt;</span> <span class="re0">$arguments</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">if</span> <span class="br0">&#40;</span><span class="sy0">!</span><span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">isProcessed</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw1">throw</span> <span class="kw2">new</span> sfException<span class="br0">&#40;</span><span class="kw3">sprintf</span><span class="br0">&#40;</span><span class="st_h">'Call to undefined method %s::%s.'</span><span class="sy0">,</span> <span class="kw3">get_class</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$method</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <span class="kw1">return</span> <span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">getReturnValue</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p><code>rest_request.method_not_found</code>イベントに登録されたすべてのリスナーはリクエストされた<code>$method</code>をテストしそれを扱うことを決定する、もしくはつぎの呼び出し可能なイベントリスナーへの移動します。リスト17-4において、このトリックを利用してサードパーティのクラスが<code>sfRestRequest</code>クラスに<code>put()</code>メソッドと<code>delete()</code>メソッドを実行時に追加する方法を見ることができます。</p>

<p>リスト17-4 - "条件を満たすまで通知する"-タイプのイベントを扱う</p>

<pre class="php"><span class="kw2">class</span> frontendConfiguration <span class="kw2">extends</span> sfApplicationConfiguration
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// ...</span>
&nbsp;
    <span class="co1">// リスナーを登録する</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">connect</span><span class="br0">&#40;</span><span class="st_h">'rest_request.method_not_found'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'sfRestRequestExtension'</span><span class="sy0">,</span> <span class="st_h">'listenToMethodNotFound'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="kw2">class</span> sfRestRequestExtension
<span class="br0">&#123;</span>
  static <span class="kw2">public</span> <span class="kw2">function</span> listenToMethodNotFound<span class="br0">&#40;</span>sfEvent <span class="re0">$event</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">switch</span> <span class="br0">&#40;</span><span class="re0">$event</span><span class="br0">&#91;</span><span class="st_h">'method'</span><span class="br0">&#93;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw1">case</span> <span class="st_h">'put'</span><span class="sy0">:</span>
        <span class="kw2">self</span><span class="sy0">::</span><span class="me2">put</span><span class="br0">&#40;</span><span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">getSubject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$event</span><span class="br0">&#91;</span><span class="st_h">'arguments'</span><span class="br0">&#93;</span><span class="br0">&#41;</span>
&nbsp;
        <span class="kw1">return</span> <span class="kw4">true</span><span class="sy0">;</span>
      <span class="kw1">case</span> <span class="st_h">'delete'</span><span class="sy0">:</span>
        <span class="kw2">self</span><span class="sy0">::</span><span class="me2">delete</span><span class="br0">&#40;</span><span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">getSubject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$event</span><span class="br0">&#91;</span><span class="st_h">'arguments'</span><span class="br0">&#93;</span><span class="br0">&#41;</span>
&nbsp;
        <span class="kw1">return</span> <span class="kw4">true</span><span class="sy0">;</span>
      <span class="kw1">default</span><span class="sy0">:</span>
        <span class="kw1">return</span> <span class="kw4">false</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span>
&nbsp;
  static protected <span class="kw2">function</span> put<span class="br0">&#40;</span><span class="re0">$restRequest</span><span class="sy0">,</span> <span class="re0">$arguments</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// putリクエストを行い結果を変数$resultに保存する</span>
    <span class="co1">// ...</span>
&nbsp;
    <span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">setReturnValue</span><span class="br0">&#40;</span><span class="re0">$result</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  static protected <span class="kw2">function</span> delete<span class="br0">&#40;</span><span class="re0">$restRequest</span><span class="sy0">,</span> <span class="re0">$arguments</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// deleteリクエストを行い結果を変数$resultに保存する</span>
    <span class="co1">// ...</span>
&nbsp;
    <span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">setReturnValue</span><span class="br0">&#40;</span><span class="re0">$result</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p>実際には、<code>notifyUntil()</code>メソッドは、mixin(サードパーティのメソッドを既存のクラスに追加する)よりも、多重継承の機能をPHPに提供します。これで新しいメソッドを継承の方法では拡張できないオブジェクトに"注入"(inject)できます。そしてこれは実行時に行われます。これでsymfonyを利用するときPHPのオブジェクト指向の機能によって制限されません。</p>

<blockquote class="tip"><p>
  <code>notifyUntil</code>イベントをキャッチする最初のリスナーはそれ以降のイベント通知を防止するので、リスナーの実行順序が不安になるかもしれません。この順序はリスナーの登録順序に対応します。つまり最初に登録されたものが最初に実行されます。しかし実際には、登録順序が原因でほかのリスナーの実行を妨げることはほとんどありません。2つのリスナーが特定のイベントで衝突することに気がついたら、一方のクラスに新たなイベント通知を追加すればよいのです。たとえばメソッド実行のはじめとおわりなどです。そして既存のクラスに新しいメソッドを追加するためにイベントを使う場合、メソッドを追加する時点でほかのものが衝突しないようにメソッドの名前をつけてください。メソッドの名前のプレフィックスをリスナークラスの名前にすることはよい習慣です。</p>
</blockquote>

<a name="changing.the.return.value.of.a.method" id="changing.the.return.value.of.a.method"></a><h3>メソッドの戻り値を変更する</h3>

<p>リスナーがイベントによって渡された情報を利用するだけでなく、通知機能のオリジナルのロジックを変更するためにそれを修正する方法も想像できるでしょう。これを実現したければ、<code>notify()</code>メソッドよりもEvent Dispatcherの<code>filter()</code>メソッドを使うべきです。すべてのイベントリスナーは2つのパラメーター: イベントオブジェクト、とフィルタリングする値で呼び出されます。値を変更するかしないかにかかわらず、イベントリスナーは値を返さなければなりません。リスト17-5はWebサービスからレスポンスをフィルタリングしてそのレスポンス内で特別な文字をエスケープするために使われる<code>filter()</code>メソッドを表示します。</p>

<p>リスト17-5 - フィルターイベントを通知して扱う</p>

<pre class="php"><span class="kw2">class</span> sfRestRequest
<span class="br0">&#123;</span>
  <span class="co1">// ...</span>
&nbsp;
  <span class="co4">/**
   * 外部のWebサービスにクエリを行う
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> fetch<span class="br0">&#40;</span><span class="re0">$uri</span><span class="sy0">,</span> <span class="re0">$parameters</span> <span class="sy0">=</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// リクエストを行い結果を$result変数に保存する</span>
    <span class="co1">// ...</span>
&nbsp;
    <span class="co1">// 取得プロセスの終了を通知する</span>
    <span class="kw1">return</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">filter</span><span class="br0">&#40;</span><span class="kw2">new</span> sfEvent<span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">,</span> <span class="st_h">'rest_request.filter_result'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span>
      <span class="st_h">'uri'</span>        <span class="sy0">=&gt;</span> <span class="re0">$uri</span><span class="sy0">,</span>
      <span class="st_h">'parameters'</span> <span class="sy0">=&gt;</span> <span class="re0">$parameters</span><span class="sy0">,</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$result</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="co1">// Webサービスのレスポンスにエスケーピング機能を追加する</span>
<span class="re0">$dispatcher</span><span class="sy0">-&gt;</span><span class="me1">connect</span><span class="br0">&#40;</span><span class="st_h">'rest_request.filter_result'</span><span class="sy0">,</span> <span class="st_h">'rest_htmlspecialchars'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="kw2">function</span> rest_htmlspecialchars<span class="br0">&#40;</span>sfEvent <span class="re0">$event</span><span class="sy0">,</span> <span class="re0">$result</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="kw1">return</span> <span class="kw3">htmlspecialchars</span><span class="br0">&#40;</span><span class="re0">$result</span><span class="sy0">,</span> <span class="kw4">ENT_QUOTES</span><span class="sy0">,</span> <span class="st_h">'UTF-8'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<a name="builtin.events" id="builtin.events"></a><h3>組み込みのイベント</h3>

<p>symfonyのクラスの多くは組み込みのイベントを持つので、クラス自身を変更することなくフレームワークを拡張できます。テーブル17-1はこれらのイベント、それぞれのタイプと引数の一覧を示しています。</p>

<p>テーブル17-1 - symfonyのイベント</p>

<table>
<thead>
<tr>
  <th><strong>イベントの名前空間</strong></th>
  <th><strong>イベントの名前</strong></th>
  <th><strong>型</strong></th>
  <th><strong>通知者</strong></th>
  <th><strong>引数</strong></th>
</tr>
</thead>
<tbody>
<tr>
  <td><strong>application</strong></td>
  <td>log</td>
  <td>notify</td>
  <td>lot of classes</td>
  <td>priority</td>
</tr>
<tr>
  <td></td>
  <td>throw_exception</td>
  <td>notifyUntil</td>
  <td>sfException</td>
  <td>-</td>
</tr>
<tr>
  <td><strong>command</strong></td>
  <td>log</td>
  <td>notify</td>
  <td>sfCommand* classes</td>
  <td>priority</td>
</tr>
<tr>
  <td></td>
  <td>pre_command</td>
  <td>notifyUntil</td>
  <td>sfTask</td>
  <td>arguments, options</td>
</tr>
<tr>
  <td></td>
  <td>post_command</td>
  <td>notify</td>
  <td>sfTask</td>
  <td>-</td>
</tr>
<tr>
  <td></td>
  <td>filter_options</td>
  <td>filter</td>
  <td>sfTask</td>
  <td>command_manager</td>
</tr>
<tr>
  <td><strong>configuration</strong></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfProjectConfiguration</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td><strong>component</strong></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfComponent</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td><strong>context</strong></td>
  <td>load_factories</td>
  <td>notify</td>
  <td>sfContext</td>
  <td>-</td>
</tr>
<tr>
  <td><strong>controller</strong></td>
  <td>change_action</td>
  <td>notify</td>
  <td>sfController</td>
  <td>module, action</td>
</tr>
<tr>
  <td></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfController</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td></td>
  <td>page_not_found</td>
  <td>notify</td>
  <td>sfController</td>
  <td>module, action</td>
</tr>
<tr>
  <td><strong>plugin</strong></td>
  <td>pre_install</td>
  <td>notify</td>
  <td>sfPluginManager</td>
  <td>channel, plugin, is_package</td>
</tr>
<tr>
  <td></td>
  <td>post_install</td>
  <td>notify</td>
  <td>sfPluginManager</td>
  <td>channel, plugin</td>
</tr>
<tr>
  <td></td>
  <td>pre_uninstall</td>
  <td>notify</td>
  <td>sfPluginManager</td>
  <td>channel, plugin</td>
</tr>
<tr>
  <td></td>
  <td>post_uninstall</td>
  <td>notify</td>
  <td>sfPluginManager</td>
  <td>channel, plugin</td>
</tr>
<tr>
  <td><strong>request</strong></td>
  <td>filter_parameters</td>
  <td>filter</td>
  <td>sfWebRequest</td>
  <td>path_info</td>
</tr>
<tr>
  <td></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfRequest</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td><strong>response</strong></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfResponse</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td></td>
  <td>filter_content</td>
  <td>filter</td>
  <td>sfResponse</td>
  <td>-</td>
</tr>
<tr>
  <td><strong>routing</strong></td>
  <td>load_configuration</td>
  <td>notify</td>
  <td>sfRouting</td>
  <td>-</td>
</tr>
<tr>
  <td><strong>task</strong></td>
  <td>cache.clear</td>
  <td>notifyUntil</td>
  <td>sfCacheClearTask</td>
  <td>app, type, env</td>
</tr>
<tr>
  <td><strong>template</strong></td>
  <td>filter_parameters</td>
  <td>filter</td>
  <td>sfViewParameterHolder</td>
  <td>-</td>
</tr>
<tr>
  <td><strong>user</strong></td>
  <td>change_culture</td>
  <td>notify</td>
  <td>sfUser</td>
  <td>culture</td>
</tr>
<tr>
  <td></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfUser</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td><strong>view</strong></td>
  <td>configure_format</td>
  <td>notify</td>
  <td>sfView</td>
  <td>format, response, request</td>
</tr>
<tr>
  <td></td>
  <td>method_not_found</td>
  <td>notifyUntil</td>
  <td>sfView</td>
  <td>method, arguments</td>
</tr>
<tr>
  <td><strong>view.cache</strong></td>
  <td>filter_content</td>
  <td>filter</td>
  <td>sfViewCacheManager</td>
  <td>response, uri, new</td>
</tr>
</tbody>
</table>

<p>これらのイベントにすべてのリスナーを自由に登録できます。呼び出し可能なリスナーは、<code>notifyUntil</code>型のイベントに登録されたときブール値を返し、<code>filter</code>型のイベントに登録されたときはフィルタリングされた値を返すことを確認してください。</p>

<p>イベントの名前空間はクラスの役割にかならずしもマッチする必要はないことに注意してください。たとえば、すべてのsymfonyのクラスは何かをログファイル(とWebデバッグツールバー)に表示する必要があるときに<code>application.log</code>イベントを通知します:</p>

<pre class="php"><span class="re0">$dispatcher</span><span class="sy0">-&gt;</span><span class="me1">notify</span><span class="br0">&#40;</span><span class="kw2">new</span> sfEvent<span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">,</span> <span class="st_h">'application.log'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="re0">$message</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre>

<p>これがつじつまが合っているとき独自のクラスは同じことを行いsymfonyのイベントに通知も行います。</p>

<a name="where.to.register.listeners" id="where.to.register.listeners"></a><h3>リスナーを登録する場所は？</h3>

<p>symfonyのリクエストの初期段階でイベントリスナーを登録することが必要です。実際には、イベントリスナーを登録する正しい場所はアプリケーションの設定クラスです。このクラスは<code>configure()</code>メソッドで使えるEvent Dispatcherへの参照を持ちます。リスト17-6は上記の例の<code>rest_request</code>イベントの1つにリスナーを登録する方法を示しています。</p>

<p>リスト17-6 - アプリケーション設定クラスにリスナーを登録する(<code>apps/frontend/config/ApplicationConfiguration.class.php</code>)</p>

<pre class="php"><span class="kw2">class</span> frontendConfiguration <span class="kw2">extends</span> sfApplicationConfiguration
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">connect</span><span class="br0">&#40;</span><span class="st_h">'rest_request.method_not_found'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'sfRestRequestExtension'</span><span class="sy0">,</span> <span class="st_h">'listenToMethodNotFound'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p>プラグイン(下記を参照)は独自のイベントリスナーを登録できます。これらのプラグインはプラグインの<code>config/config.php</code>スクリプトでこれを行います。これはアプリケーションの初期化の間に実行され<code>$this-&gt;dispatcher</code>を通してEvent Dispatcherにアクセスできます。</p>

<blockquote class="sidebar"><p class="title">
  Propelのビヘイビアー</p>
  
  <p>以前8章で説明しましたが、Propelはイベントシステムを利用します。正直に言えば、これらはsymfony1.0のイベントシステムを利用しますが、問題ありません。これらはPropelが生成したオブジェクトの拡張を有効にするためのイベントの登録と扱うコードをパッケージにします。例を見てみましょう。</p>
  
  <p>Propelオブジェクトはすべてが<code>delete()</code>メソッドを持つデータベースのテーブルに対応します。<code>delete</code>メソッドはデータベースから関連するレコードを削除します。しかし、レコードを削除できない<code>Invoice</code>クラスに関して、データベース内部のレコードを維持できるように<code>delete()</code>メソッドを変更して、<code>is_deleted</code>属性の値をtrueに変更するとよいでしょう。通常のオブジェクトの読みとりメソッド(<code>doSelect()</code>、<code>retrieveByPk()</code>)は<code>is_deleted</code>が<code>false</code>であるとレコードをみなすだけです。本当にレコードを削除できる<code>forceDelete()</code>と呼ばれる別のメソッドを追加することも必要です。実際、これらすべての修正は<code>ParanoidBehavior</code>と呼ばれる新しいクラスにまとめられます。最後の<code>Invoice</code>クラスはPropelの<code>BaseInvoice</code>クラスを継承し、ミックスインされた<code>ParanoidBehaviorMixin</code>のメソッドを持ちます。</p>
  
  <p>ビヘイビアーはPropelオブジェクト上のミックスインです。実際に、symfonyにおいて"ビヘイビアー"という用語は複数の内容をカバーします: ミックスインはプラグインとしてパッケージになります。先ほど述べた<code>ParanoidBehavior</code>クラスは<code>sfPropelParanoidBehaivorPlugin</code>と呼ばれる実在のsymfonyプラグインに対応します。とこのプラグインのインストール方法と使いかたに関する詳細な内容はsymfony公式サイトのwiki(<a href="http://www.symfony-project.org/plugins/sfPropelParanoidBehaviorPlugin">http://www.symfony-project.org/plugins/sfPropelParanoidBehaviorPlugin</a>)を参照してください。</p>
  
  <p>ビヘイビアーに関して最後の一言です: これらをサポートできるようにするには、生成されたPropelオブジェクトはかなりの数のイベント通知機能を収納しなければなりません。ビヘイビアーを使わない場合、これらが実行を遅くしてパフォーマンスに不利益をもたらすことがあります。イベントがデフォルトで有効になっていないのはそういうわけです。これを追加することでビヘイビアーのサポートを有効にするには、最初に<code>propel.ini</code>ファイルのなかの<code>propel.builder.addBehaviors</code>プロパティを<code>true</code>に設定してモデルをリビルドしなければなりません。</p>
</blockquote>

<a name="factories" id="factories"></a><h2>ファクトリ</h2>

<p>ファクトリ(factory)は特定のタスクのためのクラスの定義です。symfonyはコントローラーやセッション機能などのコア機能についてファクトリに依存します。たとえば、symfonyが新しいリクエストオブジェクトを作成する必要がある場合、symfonyはこの目的のために使うクラスの名前のためのファクトリの定義を検索します。リクエスト用のデフォルトのファクトリの定義は<code>sfWebRequest</code>クラスで、symfonyはリクエストを処理するためにこのクラスのオブジェクトを作成します。ファクトリの定義の利点はsymfonyのコア機能をとても簡単に変更できることです: ファクトリの定義を変更し、symfonyは自身の代わりにカスタムリクエストクラスを使います。</p>

<p>ファクトリの定義は<code>factories.yml</code>設定ファイルに保存されます。リスト17-7はデフォルトのファクトリの定義ファイルを示します。それぞれの定義はオートロードされるクラスの名前と(オプションとして)パラメーターの一式から構成されます。たとえば、セッションストレージのファクトリ(<code>storage:</code>キーの下で設定)は一貫したセッションを可能にするためにクライアントコンピュータ上で作成されたCookieに名前をつける<code>session_name</code>パラメーターを使います。</p>

<p>リスト17-7 - デフォルトのファクトリファイル(<code>frontend/config/factories.yml</code>)</p>

<pre><code>prod:
  logger:
    class:   sfNoLogger
    param:
      level:   err
      loggers: ~

cli:
  controller:
    class: sfConsoleController
  request:
    class: sfConsoleRequest
  response:
    class: sfConsoleResponse

test:
  storage:
    class: sfSessionTestStorage
    param:
      session_path: %SF_TEST_CACHE_DIR%/sessions

#all:
#  controller:
#    class: sfFrontWebController
#
#  request:
#    class: sfWebRequest
#    param:
#      formats:
#        txt:  text/plain
#        js:   [application/javascript, application/x-javascript, text/javascript]
#        css:  text/css
#        json: [application/json, application/x-json]
#        xml:  [text/xml, application/xml, application/x-xml]
#        rdf:  application/rdf+xml
#        atom: application/atom+xml
#
#  response:
#    class: sfWebResponse
#    param:
#      logging: %SF_LOGGING_ENABLED%
#      charset: %SF_CHARSET%
#
#  user:
#    class: myUser
#    param:
#      timeout:         1800
#      logging:         %SF_LOGGING_ENABLED%
#      use_flash:       true
#      default_culture: %SF_DEFAULT_CULTURE%
#
#  storage:
#    class: sfSessionStorage
#    param:
#      session_name: symfony
#
#  view_cache:
#    class: sfFileCache
#    param:
#      automatic_cleaning_factor: 0
#      cache_dir:                 %SF_TEMPLATE_CACHE_DIR%
#      lifetime:                  86400
#      prefix:                    %SF_APP_DIR%
#
#  i18n:
#    class: sfI18N
#    param:
#      source:               XLIFF
#      debug:                off
#      untranslated_prefix:  "[T]"
#      untranslated_suffix:  "[/T]"
#      cache:
#        class: sfFileCache
#        param:
#          automatic_cleaning_factor: 0
#          cache_dir:                 %SF_I18N_CACHE_DIR%
#          lifetime:                  86400
#          prefix:                    %SF_APP_DIR%
#
#  routing:
#    class: sfPatternRouting
#    param:
#      load_configuration: true
#      suffix:             .
#      default_module:     default
#      default_action:     index
#      variable_prefixes:  [':']
#      segment_separators: ['/', '.']
#      variable_regex:     '[\w\d_]+'
#      debug:              %SF_DEBUG%
#      logging:            %SF_LOGGING_ENABLED%
#      cache:
#        class: sfFileCache
#        param:
#          automatic_cleaning_factor: 0
#          cache_dir:                 %SF_CONFIG_CACHE_DIR%/routing
#          lifetime:                  31556926
#          prefix:                    %SF_APP_DIR%
#
#  logger:
#    class: sfAggregateLogger
#    param:
#      level: debug
#      loggers:
#        sf_web_debug:
#          class: sfWebDebugLogger
#          param:
#            condition:      %SF_WEB_DEBUG%
#            xdebug_logging: true
#        sf_file_debug:
#          class: sfFileLogger
#          param:
#            file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%.log
</code></pre>

<p>ファクトリを変更する最良の方法はデフォルトのファクトリから継承した新しいクラスを作り、新しいメソッドをそのクラスに追加することです。たとえば、ユーザーセッションのファクトリは<code>myUser</code>クラス(<code>frontend/lib/</code>ディレクトリに設置)に設定され、<code>sfUser</code>クラスから継承します。既存のファクトリを利用するには同じメカニズムを使います。リスト17-8はリクエストオブジェクトのための新しいファクトリの例を示しています。</p>

<p>リスト17-8 - ファクトリをオーバーライドする</p>

<pre class="php">//オートロードされるディレクトリ内でmyRequest.class.phpを作る
// たとえばfrontend/lib/において
<span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw2">class</span> myRequest <span class="kw2">extends</span> sfRequest
<span class="br0">&#123;</span>
  <span class="co1">// あなたのコードをここに</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="co1">// factories.ymlでクラスをリクエストファクトリとして宣言する</span>
all<span class="sy0">:</span>
  request<span class="sy0">:</span>
    <span class="kw2">class</span><span class="sy0">:</span> myRequest</pre>

<a name="integrating.with.other.frameworks.components" id="integrating.with.other.frameworks.components"></a><h2>ほかのフレームワークのコンポーネントと統合する</h2>

<p>サードパーティのクラスによって提供された機能が必要な場合、このクラスを<code>lib/</code>ディレクトリの1つにコピーしたくない場合、おそらくはsymfonyがファイルを探す通常の場所の外側にそのクラスをインストールすることになります。この場合、クラスを利用するには、オートロードを利用するsymfonyのsplオートロード統合機能を使わないかぎり、<code>requre</code>ステートメントを手動でコードに含めることになります。</p>

<p>symfonyは(まだ)すべてのためのツールを提供していません。PDFジェネレーター、Google MapsのAPI、PHPによるLucene検索エンジンの実装など、おそらくZend Frameworkからいくつかのライブラリが必要になります。PHPで直接イメージを操作する、Eメールを読むためにPOP3アカウントに接続する、コンソールのインターフェイスを設計することなどを行いたい場合、eZcomponentsからライブラリを選ぶことがあるかもしれません。幸いにして、正しい設定を定義すると、これらのライブラリからのコンポーネントはsymfonyで正常に動作します。</p>

<p>(PEAR経由でサードパーティのライブラリをインストールしないかぎり)最初に、アプリケーションの<code>app.yml</code>ファイルのなかでライブラリのrootディレクトリへのパスを宣言する必要があります:</p>

<pre><code>all:
  zend_lib_dir:   /usr/local/zend/library/
  ez_lib_dir:     /usr/local/ezcomponents/
  swift_lib_dir:  /usr/local/swiftmailer/
</code></pre>

<p>それから、symfonyがオートロードを失敗するとき、考慮するライブラリを指定することでPHPのオートロードシステムを拡張します。リスト17-9のように、オートロードクラスをアプリケーションの設定クラスに登録することでこれを行うことができます(詳細は19章を参照)。</p>

<p>リスト17-9 - サードパーティのコンポーネントのオートロードを有効にする(<code>apps/frontend/config/ApplicationConfiguration.class.php</code>)</p>

<pre class="php"><span class="kw2">class</span> frontendConfiguration <span class="kw2">extends</span> sfApplicationConfiguration
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> initialize<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">initialize</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span> <span class="co1">// 最初にsymfonyのオートロード機能をロードする</span>
&nbsp;
    <span class="co1">// Zend Frameworkを統合する</span>
    <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$sf_zend_lib_dir</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_zend_lib_dir'</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw3">set_include_path</span><span class="br0">&#40;</span><span class="re0">$sf_zend_lib_dir</span><span class="sy0">.</span>PATH_SEPARATOR<span class="sy0">.</span><span class="kw3">get_include_path</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw1">require_once</span><span class="br0">&#40;</span><span class="re0">$sf_zend_lib_dir</span><span class="sy0">.</span><span class="st_h">'/Zend/Loader.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw3">spl_autoload_register</span><span class="br0">&#40;</span><span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'Zend_Loader'</span><span class="sy0">,</span> <span class="st_h">'autoload'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <span class="co1">// eZ Componentsを統合する</span>
    <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$sf_ez_lib_dir</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_ez_lib_dir'</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw3">set_include_path</span><span class="br0">&#40;</span><span class="re0">$sf_ez_lib_dir</span><span class="sy0">.</span>PATH_SEPARATOR<span class="sy0">.</span><span class="kw3">get_include_path</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw1">require_once</span><span class="br0">&#40;</span><span class="re0">$sf_ez_lib_dir</span><span class="sy0">.</span><span class="st_h">'/Base/base.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw3">spl_autoload_register</span><span class="br0">&#40;</span><span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'ezcBase'</span><span class="sy0">,</span> <span class="st_h">'autoload'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <span class="co1">// Swift Mailerを統合する</span>
    <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$sf_swift_lib_dir</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_swift_lib_dir'</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw3">set_include_path</span><span class="br0">&#40;</span><span class="re0">$sf_swift_lib_dir</span><span class="sy0">.</span>PATH_SEPARATOR<span class="sy0">.</span><span class="kw3">get_include_path</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw1">require_once</span><span class="br0">&#40;</span><span class="re0">$sf_swift_lib_dir</span><span class="sy0">.</span><span class="st_h">'/Swift/ClassLoader.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw3">spl_autoload_register</span><span class="br0">&#40;</span><span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'Swift_ClassLoader'</span><span class="sy0">,</span> <span class="st_h">'load'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p>ロードされていないクラスの新しいオブジェクトを作るときに起きることは単純です:</p>

<ol>
<li>symfonyのオートロード機能は最初に<code>autoload.yml</code>ファイルで宣言されたパスのなかでクラスを探します。</li>
<li>クラスパスが見つからなければ、<code>spl_autoload_register()</code>によって登録されたコールバックメソッドはそれらの1つが<code>true</code>を返すまで次から次へと呼び出されます。そして<code>Zend_Loader::autoload()</code>、<code>ezcBase::autoload()</code>、と<code>Swift_ClassLoader::load()</code>のうちの1つがクラスを見つけるまでこれらが呼び出されます。</li>
<li>これらも<code>false</code>を返す場合、PHPはエラーを生成します。</li>
</ol>

<p>このことはほかのフレームワークコンポーネントはオートロードメカニズムから恩恵を受け、独自環境よりもこれらを簡単に使えることを意味します。たとえば、Lucene検索エンジンと同等のものをPHPで実装するためにZend Frameworkの<code>Zend_Search</code>コンポーネントを使いたい場合、通常は<code>require</code>ステートメントが必要です:</p>

<pre class="php"><span class="kw1">require_once</span> <span class="st_h">'Zend/Search/Lucene.php'</span><span class="sy0">;</span>
<span class="re0">$doc</span> <span class="sy0">=</span> <span class="kw2">new</span> Zend_Search_Lucene_Document<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$doc</span><span class="sy0">-&gt;</span><span class="me1">addField</span><span class="br0">&#40;</span>Zend_Search_Lucene_Field<span class="sy0">::</span><span class="me2">Text</span><span class="br0">&#40;</span><span class="st_h">'url'</span><span class="sy0">,</span> <span class="re0">$docUrl</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="co1">// ...</span></pre>

<p>symfonyとsplのオートロード機能によって、上記のコードはよりシンプルになります。<code>require</code>ステートメントを省略できるのでincludeのパスとクラスの位置に悩まなくてすみます:</p>

<pre class="php"><span class="re0">$doc</span> <span class="sy0">=</span> <span class="kw2">new</span> Zend_Search_Lucene_Document<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span> <span class="co1">// クラスがオートロードされた</span>
<span class="re0">$doc</span><span class="sy0">-&gt;</span><span class="me1">addField</span><span class="br0">&#40;</span>Zend_Search_Lucene_Field<span class="sy0">::</span><span class="me2">Text</span><span class="br0">&#40;</span><span class="st_h">'url'</span><span class="sy0">,</span> <span class="re0">$docUrl</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="co1">// ...</span></pre>

<a name="plugins" id="plugins"></a><h2>プラグイン</h2>

<p>1つのsymfonyアプリケーションのために開発したコードピースの再利用がおそらく必要になります。このコードのピースを単独のクラスのパッケージにすることができるのであれば、問題ありません: クラスを別のアプリケーションの<code>lib/</code>フォルダーの1つに設置すればオートローダが残りを引き受けます。しかし、コードが複数のファイルに散在している場合、たとえば、administrationジェネレーター用の完全に新しいテーマ、もしくは好みの視覚効果を自動化するJavaScriptファイルとヘルパーの組み合わせなどの場合、ファイルをコピーするだけの方法は最良の解決方法ではありません。</p>

<p>プラグインはいくつかのファイルにまたがるコードをパッケージにする方法と、いくつかのプロジェクトをまたがってこのコードを再利用する方法を提供します。プラグインのなかで、クラス、フィルター、イベントリスナー、ヘルパー、設定、タスク、モジュール、スキーマ、モデルの拡張、フィクスチャ、Webアセットなどをパッケージにすることができます。プラグインをインストール、アップグレード、アンインストールする方法は簡単です。これらは<code>.tgz</code>アーカイブ、PEARパッケージ、もしくはコードリポジトリとして配布可能で、コードのリポジトリから簡単にチェックアウトできます。PEARパッケージとなったプラグインは依存関係の管理機能を利用し、アップグレードと自動検出が簡単です。symfonyのロードメカニズムはプラグインを考慮し、プラグインによって提供される機能はあたかもフレームワークの一部であるかのようにプロジェクトで利用できます。</p>

<p>ですので、プラグインは基本的にsymfonyプロジェクトのために拡張機能をパッケージにしたものです。プラグインによってアプリケーションを越えて独自コードを再利用できるだけでなく、別の投稿者によって開発されたものも再利用可能でsymfonyコアにサードパーティの拡張機能を追加できます。</p>

<a name="finding.symfony.plugins" id="finding.symfony.plugins"></a><h3>symfonyのプラグインを見つける</h3>

<p>symfonyの公式サイトにはプラグイン専用のページが存在しており、つぎのURLからアクセスできます:</p>

<pre><code>http://www.symfony-project.org/plugins/
</code></pre>

<p>ここに掲載されている各プラグインはそれぞれのページでインストールについての説明やドキュメントが整備されています。</p>

<p>ここにあるプラグインはコミュニティから寄せられたものもあれば、symfonyのコア開発者が開発したものもあります。後者のものについては以下を参照してください。</p>

<ul>
<li><code>sfFeed2Plugin</code>: RSSとAtomフィードの操作を自動化する</li>
<li><code>sfThumbnailPlugin</code>: たとえばアップロードされたイメージのためにサムネイルを作る。</li>
<li><code>sfMediaLibraryPlugin</code>: メディアのアップロードと管理を可能にします。リッチテキスト内部でイメージの編集を可能にするリッチテキストエディタのための拡張機能を含む</li>
<li><code>sfShoppingCartPlugin</code>: ショッピングカートの運用を可能にする</li>
<li><code>sfPagerNavigationPlugin</code>: <code>sfPager</code>オブジェクトに基づいた古典的でAjaxによるページャーコントロールを提供する</li>
<li><code>sfGuardPlugin</code>: 認証、承認とsymfonyの標準的なセキュリティ機能を上回る別のユーザーの管理機能を提供する</li>
<li><code>sfPrototypePlugin</code>: prototypeとscript.aculo.usのJavaScriptファイルをスタンドアロンのライブラリとして提供する</li>
<li><code>sfSuperCachePlugin</code>: Webのrootディレクトリ下にキャッシュを書き出すことで、Webサーバーの処理を可能なかぎり早めます</li>
<li><code>sfOptimizerPlugin</code>: 運用環境において実行が速くなるようにアプリケーションのコードを最適化する(詳細はつぎの章を参照)</li>
<li><code>sfErrorLoggerPlugin</code>: データベースにすべての404エラーと500エラーをログに記録しこれらのエラーを閲覧するためのadministrationモジュールを提供する</li>
<li><code>sfSslRequirementPlugin</code>: アクションのためのSSL暗号化サポートを提供する</li>
</ul>

<p>公式サイトはビヘイビアー(behavior)と呼ばれる、Propelオブジェクトを拡張するために設計されたプラグインも提示します。これらのなかで、つぎのものが見つかります:</p>

<ul>
<li><code>sfPropelParanoidBehaviorPlugin</code>: オブジェクトの削除を無効にして、<code>deleted_at</code>カラムの更新で置き換える</li>
<li><code>sfPropelOptimisticLockBehaviorPlugin</code>: Propelオブジェクト用にオプティミスティックロック(楽観的ロック)を実装する</li>
</ul>

<p>公式サイトの専用ページを定期的に確認すべきです。いつも新しいプラグインが追加され、これらはWebアプリケーションのプログラミングの多くの面にとても便利なショートカットをもたらしてくれます。</p>

<p>公式サイトの専用ページは別にして、プラグインを配布するほかの方法はダウンロードのためのプラグインアーカイブを提供することと、PEARチャンネルでプラグインをホストすること、もしくは公開のバージョンコントロールリポジトリに保存することです。</p>

<a name="installing.a.plugin" id="installing.a.plugin"></a><h3>プラグインをインストールする</h3>

<p>プラグインのインストール作業はプラグインパッケージの作成方法によって異なります。つねに<code>README</code>ファイルかつ・もしくはプラグインのダウンロードのページ上のインストールの手引きを参照してください。</p>

<p>プラグインはプロジェクト単位でインストールされます。つぎのセクションで説明されるすべての方法ではすべてのプラグインのファイルを<code>myproject/plugins/pluginName/</code>ディレクトリに設置します。</p>

<a name="pear.plugins" id="pear.plugins"></a><h3>PEARプラグイン</h3>

<p>公式サイトの専用ページの一覧に記載されているプラグインはPEARチャンネル: <code>plugins:symfony-project.org</code>を通して入手できます。プラグインをインストールするには、リスト17-10で示されるように、プラグインの名前と一緒に<code>plugin:install</code>タスクを使います。</p>

<p>リスト17-10 - 公式サイトのPEARチャンネルからプラグインをインストールする</p>

<pre><code>&gt; cd myproject
&gt; php symfony plugin:install pluginName
</code></pre>

<p>代わりの方法として、プラグインをダウンロードしてディスクからインストールすることもできます。この場合、リスト17-11で示されるように、パッケージアーカイブへのパスを使います。</p>

<p>リスト17-11 - ダウンロードしたパッケージからプラグインをインストールする</p>

<pre><code>&gt; cd myproject
&gt; php symfony plugin:install /home/path/to/downloads/pluginName.tgz
</code></pre>

<p>プラグインのなかには外部のPEARチャンネルでホストされるものがあります。リスト17-12で示されるように、<code>plugin:install</code>タスクでそれらをインストールしてチャンネルを登録してチャンネルの名前を記載することを忘れないでください。</p>

<p>リスト17-12 - PEARチャンネルからプラグインをインストールする</p>

<pre><code>&gt; cd myproject
&gt; php symfony plugin:add-channel channel.symfony.pear.example.com
&gt; php symfony plugin:install --channel=channel.symfony.pear.example.com pluginName
</code></pre>

<p>これら3つのタイプのインストール方法はすべてPEARパッケージを使うので、"PEARプラグイン"という用語はsymfonyプラグインのPEARチャンネル、外部のPEARチャンネル、もしくはダウンロードしたPEARパッケージからインストールしたプラグインを区別なく説明するために使われます。</p>

<p>リスト17-13で示されているように、<code>plugin:install</code>タスクは多くのオプションをとります。</p>

<p>リスト17-13 - いくつかのオプションをつけてプラグインをインストールする</p>

<pre><code>&gt; php symfony plugin:install --stability=beta pluginName
&gt; php symfony plugin:install --release=1.0.3 pluginName
&gt; php symfony plugin:install --install-deps pluginName
</code></pre>

<blockquote class="tip"><p>
  すべてのsymfonyタスクに関しては、<code>php symfony help plugin:install</code>を実行すれば<code>plugin:install</code>のオプションと引数の説明を見ることができます</p>
</blockquote>

<a name="archive.plugins" id="archive.plugins"></a><h4>アーカイブのプラグイン</h4>

<p>プラグインのなかには単純にファイルのアーカイブとしてやってくるものがあります。それらをインストールするために、アーカイブをプロジェクトの<code>plugins/</code>ディレクトリに解凍してください。プラグインが<code>web/</code>サブディレクトリを含む場合、リスト17-14で示されるように、このディレクトリをコピーするかプロジェクトの<code>web/</code>ディレクトリにシンボリックリンクを作ることを忘れないでください。最後に、キャッシュをクリアすることを忘れないでください。</p>

<p>リスト17-14 - アーカイブからプラグインをインストールする</p>

<pre><code>&gt; cd plugins
&gt; tar -zxpf myPlugin.tgz
&gt; cd ..
&gt; ln -sf plugins/myPlugin/web web/myPlugin
&gt; php symfony cc
</code></pre>

<a name="installing.plugins.from.a.version.control.repository" id="installing.plugins.from.a.version.control.repository"></a><h4>バージョン管理システムのリポジトリからプラグインをインストールする</h4>

<p>プラグインはときにバージョン管理システム用の独自のソースコードリポジトリを持つことがあります。<code>plugins/</code>ディレクトリのなかでチェックアウトするだけでこれらのプラグインをインストールできますが、プロジェクト自身がバージョン管理システムの管理下にある場合、この作業によって問題が引き起こされる可能性があります。</p>

<p>代わりの方法として、プラグインを外部依存のライブラリとして宣言することが可能で、すべてのプロジェクトのソースコードを更新するとプラグインのソースコードも更新されます。たとえば、Subversionは<code>svn:externals</code>プロパティで外部依存を保存します。ですので、リスト17-15で示されているように、このプロパティを編集してソースコードをあとで更新することでプラグインを追加できます。</p>

<p>リスト17-15 - ソースのバージョン管理リポジトリからプラグインをインストールする</p>

<pre><code>&gt; cd myproject
&gt; svn propedit svn:externals plugins
  pluginName   http://svn.example.com/pluginName/trunk
&gt; svn up
&gt; php symfony cc
</code></pre>

<blockquote class="note"><p>
  プラグインが<code>web/</code>ディレクトリを含む場合、アーカイブのプラグインに関しては同じようにそのディレクトリへのシンボリックリンクを作らなければなりません。</p>
</blockquote>

<a name="activating.a.plugin.module" id="activating.a.plugin.module"></a><h4>プラグインモジュールを有効にする</h4>

<p>プラグインのなかにはモジュール全体を含むものがあります。プラグインモジュールと古典的なモジュールの違いはプラグインモジュールが<code>myproject/frontend/modules/</code>ディレクトリに現れないことだけです(簡単にアップグレードできる状態を保つため)。リスト17-16で示されるように、<code>settings.yml</code>ファイルのなかでこれらを有効にしなければなりません。</p>

<p>リスト17-16 - プラグインモジュールを有効にする(<code>frontend/config/settings.yml</code>)</p>

<pre><code>all:
  .settings:
    enabled_modules:  [default, sfMyPluginModule]
</code></pre>

<p>これはプラグインモジュールを必要としないアプリケーションが誤ってそのプラグインを利用できるように設定する状況を避けるためです。その状況ではセキュリティの欠陥を公開してしまう可能性があります。<code>frontend</code>モジュールと<code>backend</code>モジュールを提供するプラグインを考えてください。<code>frontend</code>モジュールは<code>frontend</code>アプリケーション専用として、<code>backend</code>モジュールは<code>backend</code>アプリケーション専用として有効にする必要があります。プラグインモジュールがデフォルトで有効にされない理由はそういうわけです。</p>

<blockquote class="tip"><p>
  defaultモジュールはデフォルトで唯一有効なモジュールです。これは本当のプラグインモジュールではありません。フレームワークの<code>$sf_symfony_lib_dir/controller/default/</code>に所属するからです。これは初期ページと、404エラー用のデフォルトページとクレデンシャルが必要なエラーページを提供するモジュールです。symfonyのデフォルトページを使いたくない場合、このモジュールを<code>enabled_modules</code>設定から除外します。</p>
</blockquote>

<a name="listing.the.installed.plugins" id="listing.the.installed.plugins"></a><h4>インストールしたプラグインの一覧を表示する</h4>

<p>プロジェクトの<code>plugins/</code>ディレクトリをざっと見るとプラグインがインストールされている場所がわかります。そして<code>plugin:list</code>タスクはより詳細な情報を示します: バージョン番号とインストールしたそれぞれのプラグインのチャンネル名です。</p>

<p>リスト17-17 - インストールされたプラグインの一覧</p>

<pre><code>&gt; cd myproject
&gt; php symfony plugin:list

Installed plugins:
sfPrototypePlugin               1.0.0-stable # plugins.symfony-project.com (symfony)
sfSuperCachePlugin              1.0.0-stable # plugins.symfony-project.com (symfony)
sfThumbnail                     1.1.0-stable # plugins.symfony-project.com (symfony)
</code></pre>

<a name="upgrading.and.uninstalling.plugins" id="upgrading.and.uninstalling.plugins"></a><h4>プラグインのアップグレードとアンインストール</h4>

<p>PEARのプラグインをアンインストールするには、リスト17-18で示されるように、プロジェクトのrootディレクトリから<code>plugin:uninstall</code>タスクを呼び出します。プラグインの名前にプラグインをインストールしたチャンネル名をプレフィックスとして追加しなければなりません(このチャンネルを決めるために<code>plugin:list</code>タスクを使います)。</p>

<p>リスト17-18 - プラグインをアンインストールする</p>

<pre><code>&gt; cd myproject
&gt; php symfony plugin:uninstall sfPrototypePlugin
&gt; php symfony cc
</code></pre>

<p>アーカイブからインストールしたプラグインもしくはSVNリポジトリからインストールしたプラグインをアンインストールするには、プロジェクトの<code>plugins/</code>と<code>web/</code>ディレクトリからプラグインのファイルを手動で削除してキャッシュをクリアします。</p>

<p>プラグインをアップグレードするには、<code>plugin:upgrade</code>タスク(PEARプラグインの場合)もしくは<code>svn update</code>を実行します(バージョン管理システムのリポジトリからプラグインを入手した場合)。アーカイブからインストールしたプラグインは簡単にアップグレードできません。</p>

<a name="anatomy.of.a.plugin" id="anatomy.of.a.plugin"></a><h3>プラグインの分析</h3>

<p>プラグインはPHPで書かれています。アプリケーションの編成方法を理解しているのであれば、プラグインの構造を理解できます。</p>

<a name="plugin.file.structure" id="plugin.file.structure"></a><h4>プラグインのファイル構造</h4>

<p>プラグインのディレクトリはおおよそプロジェクトのディレクトリと同じように編成されています。必要な時にsymfonyによって自動的にロードされるようにするためにプラグインファイルは正しいディレクトリに存在しなければなりません。ファイル構造の記述に関してはリスト17-19をご覧ください。</p>

<p>リスト17-19 - プラグインのファイル構造</p>

<pre><code>pluginName/
  config/
    *schema.yml        // データスキーマ
    *schema.xml
    config.php         // 特定のプラグイン設定
  data/
    generator/
      sfPropelAdmin
        */             // administrationジェネレーターテーマ
          template/
          skeleton/
    fixtures/
      *.yml            // フィクスチャファイル
  lib/
    *.php              // クラス
    helper/
      *.php            // ヘルプ
    model/
      *.php            // モデルクラス
    task/
      *Task.class.php  // CLIタスク
  modules/
    */                 // モジュール
      actions/
        actions.class.php
      config/
        module.yml
        view.yml
        security.yml
      templates/
        *.php
      validate/
        *.yml
  web/
    *                  // アセット
</code></pre>

<a name="plugin.abilities" id="plugin.abilities"></a><h4>プラグインの機能</h4>

<p>プラグインは多くのものを含みます。コマンドラインでタスクを呼び出すときに実行中のアプリケーションはこれらの内容を自動的に考慮します。しかしプラグインを適切に機能させるには、いくつかの規約を遵守しなければなりません:</p>

<ul>
<li>データベースのスキーマは<code>propel-</code>タスクによって検出されます。<code>propel-build-model</code>タスクを呼び出すと、プロジェクトモデルとすべてのプラグインモデルがリビルドされます。リスト17-20で示されるように、 プラグインスキーマはつねに<code>plugins.pluginName.lib.model</code>形式で<code>package</code>属性を持つことに注意してください。</li>
</ul>

<p>リスト17-20 - スキーマ宣言の例(<code>myPlugin/config/schema.yml</code>)</p>

<pre><code>propel:
  _attributes:    { package: plugins.myPlugin.lib.model }
  my_plugin_foobar:
    _attributes:    { phpName: myPluginFoobar }
      id:
      name:           { type: varchar, size: 255, index: unique }
      ...
</code></pre>

<ul>
<li>プラグインの設定はプラグインのブートストラップスクリプト(<code>config.php</code>)に含まれています。このファイルはアプリケーションとプロジェクト設定のあとで実行されるので、symfonyはその時点ですでに起動しています。たとえば、既存のクラスをイベントリスナーもしくはビヘイビアーで拡張するためです。</li>
<li>プラグインの<code>data/fixtures/</code>ディレクトリに設置されたフィクスチャファイルは<code>propel:load-data</code>タスクで処理されます。</li>
<li>プロジェクトの<code>lib/</code>フォルダーに設置されたクラスのようにカスタムクラスはオートロードされます。</li>
<li>テンプレートのなかで<code>use_helper()</code>ヘルパーを呼び出すときにヘルパーは自動的に発見されます。これらはプラグインの<code>lib/</code>ディレクトリの1つの<code>helper/</code>サブディレクトリに存在しなければなりません。</li>
<li><code>myplugin/lib/model/</code>ディレクトリ内のモデルクラスは(<code>myplugin/lib/model/om/</code>ディレクトリと<code>myplugin/lib/model/map/</code>ディレクトリ)内部のPropelビルダによって生成されたモデルクラスを専門に扱います。もちろんこれらもオートロードされます。独自プロジェクトのディレクトリ内で生成されたプラグインのモデルクラスはオーバーライドできません。</li>
<li>プラグインをインストールすればタスクはsymfonyコマンドですぐに利用できます。プラグインは新しいタスクを追加もしくは既存のものをオーバーライドできます。タスク用にプラグインの名前を名前空間として使うことは最良の習慣です。プラグインに追加されたものを含めて、利用可能なタスクの一覧を見るには、<code>php symfony</code>を入力してください。</li>
<li>アプリケーションの<code>enabled_modules</code>設定で宣言すれば、モジュールは外部からアクセス可能な新しいアクションを提供します。</li>
<li>サーバーはWebアセット(イメージ、スクリプト、スタイルシートなど)を利用できます。コマンドライン経由でプラグインをインストールしたとき、システムが許可するのであればsymfonyはプロジェクトの<code>web/</code>ディレクトリにシンボリックリンクを作るもしくは<code>web/</code>ディレクトリの内容をプロジェクトのディレクトリにコピーします。プラグインがアーカイブもしくはバージョン管理ツールのリポジトリからインストールされた場合、手動で<code>web/</code>ディレクトリにコピーしなければなりません(プラグインに添付されている<code>README</code>に記載されています)。</li>
</ul>

<blockquote class="tip"><p> <strong>ルーティングルールをプラグインに登録する</strong></p>
  
  <p>プラグインはルーティングシステムに新しいルールを追加できますが、そのためにカスタム<code>routing.yml</code>設定ファイルを使用できません。これはルールが定義された順序が非常に重要で、symfonyのYAMLファイルのシンプルなカスケード設定システムはこのジョン所をごちゃごちゃにするからです。代わりにプラグインはイベントリスナーを<code>routing.load_configuration</code>イベントに登録してリスナーの先頭にルールを手動で追加する必要があります:</p>

<pre class="php"><span class="co1">// plugins/myPlugin/config/config.phpのなか</span>
<span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">dispatcher</span><span class="sy0">-&gt;</span><span class="me1">connect</span><span class="br0">&#40;</span><span class="st_h">'routing.load_configuration'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'myPluginRouting'</span><span class="sy0">,</span> <span class="st_h">'listenToRoutingLoadConfigurationEvent'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// plugins/myPlugin/lib/myPluginRouting.phpのなか</span>
<span class="kw2">class</span> myPluginRouting
<span class="br0">&#123;</span>
  static <span class="kw2">public</span> <span class="kw2">function</span> listenToRoutingLoadConfigurationEvent<span class="br0">&#40;</span>sfEvent <span class="re0">$event</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$routing</span> <span class="sy0">=</span> <span class="re0">$event</span><span class="sy0">-&gt;</span><span class="me1">getSubject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="co1">// 既存のルーティングルールの上にプラグインのルールを追加する</span>
    <span class="re0">$routing</span><span class="sy0">-&gt;</span><span class="me1">prependRoute</span><span class="br0">&#40;</span><span class="st_h">'my_route'</span><span class="sy0">,</span> <span class="st_h">'/my_plugin/:action'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'module'</span> <span class="sy0">=&gt;</span> <span class="st_h">'myPluginAdministrationInterface'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>
</blockquote>

<a name="manual.plugin.setup" id="manual.plugin.setup"></a><h4>手動によるプラグインのセットアップ</h4>

<p><code>plugin:install</code>タスクが独自に処理できない要素がいくつかあります。インストール作業の間にこれらを手動でセットアップする必要があります:</p>

<ul>
<li>カスタムアプリケーション設定はプラグインのコードで使われますが(たとえば、<code>sfConfig::get('app_myplugin_foo')</code>を利用する)、デフォルト値をプラグインの<code>config/</code>ディレクトリに設置された<code>app.yml</code>ファイルに設定できません。デフォルト値を処理するには、<code>sfConfig::get()</code>メソッドの2番目の引数を使います。設定はまだアプリケーションレベルでオーバーライドできます(リスト17-26で例をご覧ください)。</li>
<li>カスタムルーティングルールはアプリケーションの<code>routing.yml</code>に手動で追加しなければなりません。</li>
<li>カスタムフィルターはアプリケーションの<code>filters.yml</code>に手動で追加しなければなりません。</li>
<li>カスタムファクトリはアプリケーションの<code>factories.yml</code>に手動で追加しなければなりません。</li>
</ul>

<p>一般的に言えば、アプリケーションの設定ファイルの1つに帰結するようなすべての設定は手動で追加しなければなりません。このような手動のセットアップが必要なプラグインは<code>README</code>ファイルで詳細なインストール方法を説明しています。</p>

<a name="customizing.a.plugin.for.an.application" id="customizing.a.plugin.for.an.application"></a><h4>アプリケーションのためにプラグインをカスタマイズする</h4>

<p>プラグインをカスタマイズしたいときは、決して<code>plugins/</code>ディレクトリ内で見つかるコードを変更してはなりません。これを行うと、プラグインをアップグレードするときにすべての修正内容が失われてしまいます。必要なカスタマイズを行うために、プラグインはカスタム設定を提供し、オーバーライドをサポートします。</p>

<p>リスト17-21で示されるように、よく設計されたプラグインはアプリケーションの<code>app.yml</code>ファイルで変更できる設定を利用します。</p>

<p>リスト17-21 - アプリケーションの設定を利用するプラグインをカスタマイズする</p>

<pre class="php"><span class="co1">// プラグインのコードの例</span>
<span class="re0">$foo</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_my_plugin_foo'</span><span class="sy0">,</span> <span class="st_h">'bar'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// アプリケーションのapp.ymlで'foo'のデフォルト値('bar')を変更する</span>
all<span class="sy0">:</span>
  my_plugin<span class="sy0">:</span>
    foo<span class="sy0">:</span>       barbar</pre>

<p>モジュールの設定とデフォルト値はプラグインの<code>README</code>ファイルで詳しく説明されています。</p>

<p>独自のアプリケーション内部で同じ名前のモジュールを作成することでプラグインモジュールのデフォルトの内容を置き換えることができます。プラグイン要素の代わりにアプリケーション要素が使われているので、本当のオーバーライドではありません。プラグインの名前と同じ名前のテンプレートと設定ファイルを作ればプラグインモジュールは立派に機能します。</p>

<p>一方で、アクションをオーバーライドする機能を持つモジュールをプラグインに持たせたい場合、プラグインモジュール内の<code>actions.class.php</code>のメソッドがアプリケーションモジュールの<code>actions.class.php</code>によって継承できるように、<code>actions.class.php</code>は空でなければならずオートロードクラスから継承しなければなりません。お手本に関してはリスト17-22を参照してください。</p>

<p>リスト17-22 - プラグインのアクションをカスタマイズする</p>

<pre class="php"><span class="co1">// myPlugin/modules/mymodule/lib/myPluginmymoduleActions.class.phpのなか</span>
<span class="kw2">class</span> myPluginmymoduleActions <span class="kw2">extends</span> sfActions
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> executeIndex<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// ここに何らかのコード</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="co1">// myPlugin/modules/mymodule/actions/actions.class.phpにて</span>
&nbsp;
<span class="kw1">require_once</span> <span class="kw3">dirname</span><span class="br0">&#40;</span><span class="kw4">__FILE__</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/../lib/myPluginmymoduleActions.class.php'</span><span class="sy0">;</span>
&nbsp;
<span class="kw2">class</span> mymoduleActions <span class="kw2">extends</span> myPluginmymoduleActions
<span class="br0">&#123;</span>
  <span class="co1">// なし</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="co1">// frontend/modules/mymodule/actions/actions.class.phpにて</span>
<span class="kw2">class</span> mymoduleActions <span class="kw2">extends</span> myPluginmymoduleActions
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> executeIndex<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// ここでプラグインのコードをオーバーライドする</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<blockquote class="sidebar"><p class="title">
  <strong>symfony 1.1の新しい機能</strong>: プラグインのスキーマをカスタマイズする</p>
  
  <p>モデルをビルドするとき、symfonyは、つぎのルールにしたがって、プラグインのものを含めて、それぞれの既存のスキーマのためにカスタムYAMLファイルを探します:</p>
  
  <table>
  <thead>
  <tr>
    <th>オリジナルのスキーマ名</th>
    <th>カスタムスキーマ名</th>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>config/schema.yml</td>
    <td>schema.custom.yml</td>
  </tr>
  <tr>
    <td>config/foobar_schema.yml</td>
    <td>foobar_schema.custom.yml</td>
  </tr>
  <tr>
    <td>plugins/myPlugin/config/schema.yml</td>
    <td>myPlugin_schema.custom.yml</td>
  </tr>
  <tr>
    <td>plugins/myPlugin/config/foo_schema.yml</td>
    <td>myPlugin_foo_schema.custom.yml</td>
  </tr>
  </tbody>
  </table>
  
  <p>カスタムスキーマはアプリケーションとプラグインの<code>config/</code>ディレクトリを探すので、プラグインは別のプラグインのスキーマをオーバーライドをして、スキーマ単位で複数のカスタマイズが可能です。</p>
  
  <p>symfonyはそれぞれのテーブルの<code>phpName</code>に基づいて2つのスキーマをマージします。マージ処理によってテーブル、カラム、カラムの属性の追加もしくは修正できます。たとえば、つぎの一覧はカスタムスキーマがカラムをプラグインのスキーマで定義されたテーブルに追加する方法を示しています。</p>

<pre><code># オリジナルのスキーマ(plugins/myPlugin/config/schema.yml)
propel:
  article:
    _attributes:    { phpName: Article }
    title:          varchar(50)
    user_id:        { type: integer }
    created_at:

# カスタムスキーマ(myPlugin_schema.custom.yml)
propel:
  article:
    _attributes:    { phpName: Article, package: foo.bar.lib.model }
    stripped_title: varchar(50)

# スキーマの結果、内部でマージされモデルとSQLの生成用に内部で使われる
propel:
  article:
    _attributes:    { phpName: Article, package: foo.bar.lib.model }
    title:          varchar(50)
    user_id:        { type: integer }
    created_at:
    stripped_title: varchar(50)
</code></pre>
  
  <p>マージ処理はテーブルの<code>phpName</code>をキーとして使うので、スキーマのなかで同じ<code>phpName</code>を保つのであれば、データベース内のプラグインテーブルの名前を変更できます。</p>
</blockquote>

<a name="how.to.write.a.plugin" id="how.to.write.a.plugin"></a><h3>プラグインの書き方</h3>

<p><code>plugin:install</code>タスクではPEARパッケージ形式のプラグインのみがインストールされます。このようなプラグインは公式サイトの専用ページ、PEARチャンネル経由もしくはダウンロードできる通常のファイルとして配布されていることを覚えておいてください。プラグインを編集したい場合は、単純なアーカイブよりもPEARパッケージとして公開したほうがベターでしょう。加えて、プラグインをPEARパッケージにすればアップグレード作業が簡単になり、依存関係の宣言が可能で、自動的にアセットを<code>web/</code>ディレクトリにデプロイできます。</p>

<a name="file.organization" id="file.organization"></a><h4>ファイルのコンフィギュレーション</h4>

<p>新しい機能を開発し、プラグインとしてパッケージにすることを考えてみましょう。最初の段階はファイルを論理的に編成して、symfonyのロードメカニズムが必要なときにこれらのファイルを見つけることができるようにしましょう。この目的のために、リスト17-19で示されているディレクトリ構造に従う必要があります。リスト17-23は<code>sfSamplePlugin</code>プラグインのためのファイル構造の例を示しています。</p>

<p>リスト17-23 - プラグインとしてパッケージにするファイルの一覧の例</p>

<pre><code>sfSamplePlugin/
  README
  LICENSE
  config/
    schema.yml
  data/
    fixtures/
      fixtures.yml
  lib/
    model/
      sfSampleFooBar.php
      sfSampleFooBarPeer.php
    task/
      sfSampleTask.class.php
    validator/
      sfSampleValidator.class.php
  modules/
    sfSampleModule/
      actions/
        actions.class.php
      config/
        security.yml
      lib/
        BasesfSampleModuleActions.class.php
      templates/
        indexSuccess.php
  web/
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png
</code></pre>

<p>編集に関して、プラグインのディレクトリの位置(リスト17-23の<code>sfSamplePlugin/</code>)は重要ではありません。これはディスク上の任意の場所に設置できます。</p>

<blockquote class="tip"><p>
  既存のプラグインを練習問題として考え、初めてプラグインを作るさいには、これらの名前の規約とファイルの構造を再現してみてください。</p>
</blockquote>

<a name="creating.the.package.xml.file" id="creating.the.package.xml.file"></a><h4>package.xmlファイルを作る</h4>

<p>プラグイン編集のつぎの段階はプラグインディレクトリのrootで<code>package.xml</code>ファイルを追加することです。<code>package.xml</code>はPEARの構文に従います。リスト17-24の典型的なsymfonyプラグインの<code>package.xml</code>をご覧ください。</p>

<p>リスト17-24 - symfonyプラグイン用の<code>package.xml</code></p>

<pre class="xml"><span class="sc3"><span class="re1">&lt;?xml</span> <span class="re0">version</span>=<span class="st0">&quot;1.0&quot;</span> <span class="re0">encoding</span>=<span class="st0">&quot;UTF-8&quot;</span><span class="re2">?&gt;</span></span>
<span class="sc3"><span class="re1">&lt;package</span> <span class="re0">packagerversion</span>=<span class="st0">&quot;1.4.6&quot;</span> <span class="re0">version</span>=<span class="st0">&quot;2.0&quot;</span> <span class="re0">xmlns</span>=<span class="st0">&quot;http://pear.php.net/dtd/package-2.0&quot;</span> <span class="re0">xmlns:tasks</span>=<span class="st0">&quot;http://pear.php.net/dtd/tasks-1.0&quot;</span> <span class="re0">xmlns:xsi</span>=<span class="st0">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span> <span class="re0">xsi:schemaLocation</span>=<span class="st0">&quot;http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd&quot;</span><span class="re2">&gt;</span></span>
 <span class="sc3"><span class="re1">&lt;name<span class="re2">&gt;</span></span></span>sfSamplePlugin<span class="sc3"><span class="re1">&lt;/name<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;channel<span class="re2">&gt;</span></span></span>plugins.symfony-project.org<span class="sc3"><span class="re1">&lt;/channel<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;summary<span class="re2">&gt;</span></span></span>symfony sample plugin<span class="sc3"><span class="re1">&lt;/summary<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;description<span class="re2">&gt;</span></span></span>Just a sample plugin to illustrate PEAR packaging<span class="sc3"><span class="re1">&lt;/description<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;lead<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;name<span class="re2">&gt;</span></span></span>Fabien POTENCIER<span class="sc3"><span class="re1">&lt;/name<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;user<span class="re2">&gt;</span></span></span>fabpot<span class="sc3"><span class="re1">&lt;/user<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;email<span class="re2">&gt;</span></span></span>fabien.potencier@symfony-project.com<span class="sc3"><span class="re1">&lt;/email<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;active<span class="re2">&gt;</span></span></span>yes<span class="sc3"><span class="re1">&lt;/active<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;/lead<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;date<span class="re2">&gt;</span></span></span>2006-01-18<span class="sc3"><span class="re1">&lt;/date<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;time<span class="re2">&gt;</span></span></span>15:54:35<span class="sc3"><span class="re1">&lt;/time<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;version<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;release<span class="re2">&gt;</span></span></span>1.0.0<span class="sc3"><span class="re1">&lt;/release<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;api<span class="re2">&gt;</span></span></span>1.0.0<span class="sc3"><span class="re1">&lt;/api<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;/version<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;stability<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;release<span class="re2">&gt;</span></span></span>stable<span class="sc3"><span class="re1">&lt;/release<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;api<span class="re2">&gt;</span></span></span>stable<span class="sc3"><span class="re1">&lt;/api<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;/stability<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;license</span> <span class="re0">uri</span>=<span class="st0">&quot;http://www.symfony-project.org/license&quot;</span><span class="re2">&gt;</span></span>MIT license<span class="sc3"><span class="re1">&lt;/license<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;notes<span class="re2">&gt;</span></span></span>-<span class="sc3"><span class="re1">&lt;/notes<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;contents<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;/&quot;</span><span class="re2">&gt;</span></span>
   <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;README&quot;</span> <span class="re2">/&gt;</span></span>
   <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;LICENSE&quot;</span> <span class="re2">/&gt;</span></span>
   <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;config&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc-1">&lt;!-- model --&gt;</span>
    <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;schema.yml&quot;</span> <span class="re2">/&gt;</span></span>
   <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;data&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;fixtures&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc-1">&lt;!-- fixtures --&gt;</span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;fixtures.yml&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;lib&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;model&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc-1">&lt;!-- model classes --&gt;</span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleFooBar.php&quot;</span> <span class="re2">/&gt;</span></span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleFooBarPeer.php&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;task&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc-1">&lt;!-- tasks --&gt;</span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleTask.class.php&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;validator&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc-1">&lt;!-- validators --&gt;</span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleValidator.class.php&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;modules&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleModule&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;actions/actions.class.php&quot;</span> <span class="re2">/&gt;</span></span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;config/security.yml&quot;</span> <span class="re2">/&gt;</span></span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;lib/BasesfSampleModuleActions.class.php&quot;</span> <span class="re2">/&gt;</span></span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;templates/indexSuccess.php&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;web&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;css&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc-1">&lt;!-- stylesheets --&gt;</span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleStyle.css&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;dir</span> <span class="re0">name</span>=<span class="st0">&quot;images&quot;</span><span class="re2">&gt;</span></span>
     <span class="sc-1">&lt;!-- images --&gt;</span>
     <span class="sc3"><span class="re1">&lt;file</span> <span class="re0">role</span>=<span class="st0">&quot;data&quot;</span> <span class="re0">name</span>=<span class="st0">&quot;sfSampleImage.png&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;/dir<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;/contents<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;dependencies<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;required<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;php<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;min<span class="re2">&gt;</span></span></span>5.1.0<span class="sc3"><span class="re1">&lt;/min<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/php<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;pearinstaller<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;min<span class="re2">&gt;</span></span></span>1.4.1<span class="sc3"><span class="re1">&lt;/min<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/pearinstaller<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;package<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;name<span class="re2">&gt;</span></span></span>symfony<span class="sc3"><span class="re1">&lt;/name<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;channel<span class="re2">&gt;</span></span></span>pear.symfony-project.com<span class="sc3"><span class="re1">&lt;/channel<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;min<span class="re2">&gt;</span></span></span>1.1.0<span class="sc3"><span class="re1">&lt;/min<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;max<span class="re2">&gt;</span></span></span>1.2.0<span class="sc3"><span class="re1">&lt;/max<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;exclude<span class="re2">&gt;</span></span></span>1.2.0<span class="sc3"><span class="re1">&lt;/exclude<span class="re2">&gt;</span></span></span>
   <span class="sc3"><span class="re1">&lt;/package<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;/required<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;/dependencies<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;phprelease</span> <span class="re2">/&gt;</span></span>
 <span class="sc3"><span class="re1">&lt;changelog</span> <span class="re2">/&gt;</span></span>
<span class="sc3"><span class="re1">&lt;/package<span class="re2">&gt;</span></span></span></pre>

<p>ここで注目すべき部分は<code>&lt;contents&gt;</code>タグと<code>&lt;dependencies&gt;</code>タグで、つぎに説明します。残りのタグに関しては、symfony固有のものではありませんので、<code>package.xml</code>フォーマットに関する詳細な内容はPEARオンラインマニュアル(<a href="http://pear.php.net/manual/">http://pear.php.net/manual/</a>) を参照してください。</p>

<a name="contents" id="contents"></a><h4>内容</h4>

<p><code>&lt;contents&gt;</code>タグはプラグインのファイル構造を記述しなければならない場所です。このタグはコピーするファイルとその場所をPEARに伝えます。<code>&lt;dir&gt;</code>タグと<code>&lt;file&gt;</code>タグでファイル構造を記述してください。すべての<code>&lt;file&gt;</code>タグは<code>role="data"</code>属性を持たなければなりません。リスト17-24の<code>&lt;contents&gt;</code>タグの部分はリスト17-23の正しいディレクトリ構造を記載しています。</p>

<blockquote class="note"><p>
  <code>&lt;dir&gt;</code>タグの使用は義務ではありません。<code>&lt;file&gt;</code>タグ内で相対パスを<code>name</code>の値として利用できるからです。<code>package.xml</code>ファイルを読みやすくするためにお勧めです。</p>
</blockquote>

<a name="plugin.dependencies" id="plugin.dependencies"></a><h4>プラグインの依存関係</h4>

<p>任意のバージョンのPHP、PEAR、symfony、PEARパッケージ、もしくはほかのプラグインの一式で動くようにプラグインは設計されています。<code>&lt;dependencies&gt;</code>タグでこれらの依存関係を宣言すれば必要なパッケージがすでにインストールされていることを確認してそうでなければ例外を起動するようPEARに伝えることになります。</p>

<p>最小要件として、少なくとも開発環境に対応したPHP、PEARとsymfonyへの依存関係をつねに宣言します。何を追加すればよいのかわからなければ、PHP 5.1、PEAR 1.4とsymfony 1.0の要件を追加してください。</p>

<p>それぞれのプラグインに対してsymfonyの最大のバージョン番号を追加することも推奨されます。これによって上位バージョンのsymfonyでプラグインを使うときにエラーメッセージが表示され、プラグインを再リリースするまえにこのバージョンでプラグインが正しく動作するのかを確認することをプラグインの作者に義務づけます。無言でプラグインの動作が失敗するよりも警告を発してダウンロードとアップグレードするほうがベターです。</p>

<p>プラグインを依存関係のあるものとして指定すれば、ユーザーはプラグインとすべての依存関係を1つのコマンドでインストールできるようになります:</p>

<pre><code>&gt; php symfony plugin:install --install-deps sfSamplePlugin
</code></pre>

<a name="building.the.plugin" id="building.the.plugin"></a><h4>プラグインをビルドする</h4>

<p>PEARコンポーネントはパッケージの<code>.tgz</code>アーカイブを作るコマンド(<code>pear package</code>)を持ちます。リスト17-25では、<code>package.xml</code>を含むディレクトリでこのコマンドを呼び出しています。</p>

<p>リスト17-25 - プラグインをPEARパッケージにする</p>

<pre><code>&gt; cd sfSamplePlugin
&gt; pear package

Package sfSamplePlugin-1.0.0.tgz done
</code></pre>

<p>いったんプラグインのパッケージがビルドされたら、リスト17-26で示されるように、あなたの環境にこれをインストールして動作を確認してください。</p>

<p>リスト17-26 - プラグインをインストールする</p>

<pre><code>&gt; cp sfSamplePlugin-1.0.0.tgz /home/production/myproject/
&gt; cd /home/production/myproject/
&gt; php symfony plugin:install sfSamplePlugin-1.0.0.tgz
</code></pre>

<p><code>&lt;contents&gt;</code>タグにある説明にしたがって、パッケージにされたファイルは最終的にプロジェクトの異なるディレクトリに設置されます。リスト17-27はインストールのあとで<code>sfSamplePlugin</code>のファイルが設置される場所を示しています。</p>

<p>リスト17-27 - プラグインファイルは<code>plugin/</code>ディレクトリと<code>web/</code>ディレクトリにインストールされる</p>

<pre><code>plugins/
  sfSamplePlugin/
    README
    LICENSE
    config/
      schema.yml
    data/
      fixtures/
        fixtures.yml
    lib/
      model/
        sfSampleFooBar.php
        sfSampleFooBarPeer.php
      task/
        sfSampleTask.class.php
      validator/
        sfSampleValidator.class.php
    modules/
      sfSampleModule/
        actions/
          actions.class.php
        config/
          security.yml
        lib/
          BasesfSampleModuleActions.class.php
        templates/
          indexSuccess.php
web/
  sfSamplePlugin/               ## システム次第で、コピーもしくはシンボリックリンク
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png
</code></pre>

<p>このプラグインのふるまいをアプリケーションでテストしてください。きちんと動くのであれば、プラグインを複数のプロジェクトにまたがって配布するもしくはsymfonyコミュニティに寄付する準備ができています。</p>

<a name="hosting.your.plugin.in.the.symfony.project.website" id="hosting.your.plugin.in.the.symfony.project.website"></a><h4>公式サイトでプラグインを配布する</h4>

<p>symfonyのプラグインは以下の手順にしたがって<code>symfony-project.org</code>のWebサイトで配布されるときにもっとも幅広い利用者を得ます。独自プラグインをつぎのような方法で配布できます:</p>

<ol>
<li><code>README</code>ファイルにプラグインのインストール方法と使いかたが、<code>LICENSE</code>ファイルにはライセンスの詳細が記述されていることを確認する。<code>README</code>はMarkdownの構文 (<a href="http://daringfireball.net/projects/markdown/syntax">http://daringfireball.net/projects/markdown/syntax</a>) で記述する。</li>
<li>公式サイトのアカウント (http://www.symfony-project.org/user/new) を作りプラグインのページ (http://www.symfony-project.org/plugins/new) を作る。</li>
<li><code>pear package</code>コマンドを呼び出してプラグイン用のPEARパッケージを作りテストする。PEARパッケージの名前は<code>sfSamplePlugin-1.0.0.tgz</code> (1.0.0はプラグインのバージョン)でなければならない。</li>
<li>PEARパッケージをアップロードする (<code>sfSamplePlugin-1.0.0.tgz</code>)。</li>
<li>アップロードしたプラグインは一覧ページ (<a href="http://www.symfony-project.org/plugins/">http://www.symfony-project.org/plugins/</a>) に表示される。</li>
</ol>

<p>この手続きを行えば、ユーザーはプロジェクトのディレクトリでつぎのコマンドを入力するだけでプラグインをインストールできるようになります:</p>

<pre><code>&gt; php symfony plugin:install sfSamplePlugin
</code></pre>

<a name="naming.conventions" id="naming.conventions"></a><h4>命名規約</h4>

<p><code>plugin/</code>ディレクトリをきれいに保つために、すべてのプラグインの名前がcamelCaseであり<code>Plugin</code>のサフィックスで終わることを確認してください(たとえば、<code>shoppingCartPlugin</code>、<code>feedPlugin</code>)。プラグインに名前をつけるまえに、同じ名前のプラグインが存在しないことを確認してください。</p>

<blockquote class="note"><p>
  Propelに依存するプラグインの名前は<code>Propel</code>を含みます。たとえば、Propelのデータアクセスオブジェクトを利用する認証プラグインは<code>sfPropelAuth</code>という名前になります。</p>
</blockquote>

<p>プラグインには使用条件と選んだライセンスを説明する<code>LICENSE</code>ファイルをつねに含めなければなりません。バージョンの履歴、プラグインの目的、効果、インストールと設定の手引きなどを含めることも推奨されます。</p>

<a name="summary" id="summary"></a><h2>まとめ</h2>

<p>symfonyのクラスはアプリケーションレベルで修正できる機能を提供する<code>sfMixer</code>フックを含みます。ミックスイン(mixin)のメカニズムはPHPの制約が禁止している実行時のクラスの多重継承とオーバーライドを可能にします。ですのでそのためにコアクラスを修正しなければならないとしても、またファクトリ(factory)の設定がそこに存在するとしてもsymfonyの機能を簡単に拡張できます。</p>

<p>すでに多くの拡張機能(エクステンション)が存在し、プラグインとしてパッケージが作成されています。symfonyのコマンドラインによってインストール、アップグレード、アンインストールするのが簡単です。プラグインをPEARパッケージを作成するのと同じぐらい簡単で、複数のアプリケーションをまたがって再利用できます。</p>

<p>symfony公式サイトのwikiには多くのプラグインが含まれ、あなた自身のプラグインも追加できます。これであなたは方法を理解したので、私たちsymfonyの開発者はあなたが多くの便利な拡張機能でsymfonyコアを強化して下さることを望んでおります！</p>
</div>
<div class="navigation">
<hr/>
<table width="100%">
<tr>
<td width="40%" align="left"><a href="16-Application-Management-Tools.html">前の章</a></td>
<td width="20%" align="center"><a href="index.html">ホーム</a></td>
<td width="40%" align="right"><a href="18-Performance.html">次の章</a></td>
</tr>
</table>

</div>
</body>

</html>
